/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package netwrokproject;

import java.util.ArrayDeque;
import java.util.concurrent.Semaphore;

/**
 *
 * @author Arquitetura
 */
public abstract class Layer {
    // Nome unico para a camada, usado apenas em debug
    protected String name;
    // Apontadores para a camada de cima e a de baixo
    protected Layer layerUP,  layerDOWN;
    // Fila de mensagens recebidas da camada de baixo, onde uma mensagem e' uma ArrayDeque<Byte>
    protected ArrayDeque<ArrayDeque<Byte>> receivedMessages;
    // Fila de mensagens vindas da camada de cima para serem enviados
    protected ArrayDeque<ArrayDeque<Byte>> sentMessages;
    //Semaforo tipo produtor-consumidor para a fila sentMessages
    protected Semaphore semaphoreSend;
    //Semaforo tipo produtor-consumidor para a fila de receivedMessages
    protected Semaphore semaphoreReceive;

    private static boolean printTracks;


    public Layer() {
        initComponents();
    }

    /**
     * Construtor para debugs
     */
    public Layer(String name) {
        this.name = name;
        initComponents();
    }

    /**
     * Inicializa os atributos
     */
    private void initComponents() {
        semaphoreSend = new Semaphore(0);
        semaphoreReceive = new Semaphore(0);
        receivedMessages = new ArrayDeque<ArrayDeque<Byte>>();
        sentMessages = new ArrayDeque<ArrayDeque<Byte>>();
    }

    public String getName() {
        return name;
    }

    public Layer getLayerDOWN() {
        return layerDOWN;
    }

    public Layer getLayerUP() {
        return layerUP;
    }

    public boolean isPrintTracks() {
        return printTracks;
    }

    public void setLayerDOWN(Layer down) {
        this.layerDOWN = down;
    }

    public void setLayerUP(Layer up) {
        this.layerUP = up;
    }

    public static void setPrintTracks(boolean printTracks) {
        Layer.printTracks = printTracks;
    }

    /**
     * IMPORTANTE: Este metodo  deve ser usado apenas em sendMessage()
     * ou em receiveMessage() quando se quiser passar uma mensagem para
     * a camada de baixo tratar e enviar, chamando "layerDOWN.send(mensagemTratada);"
     */
    public synchronized void send(ArrayDeque<Byte> mensagem) {
        sentMessages.addLast(mensagem);
        semaphoreSend.release();
    }

    /**
     * IMPORTANTE: Este metodo  deve ser usado apenas em sendMessage()
     * ou em receiveMessage() quando se quiser passar uma mensagem para
     * a camada de cima tratar e enviar, chamando "layerUP.receive(mensagemTratada);"
     */
    public synchronized void receive(ArrayDeque<Byte> mensagem) {
        receivedMessages.addLast(mensagem);
        semaphoreReceive.release();
    }

    /**
     * Metodo executado sempre quando uma mensagem chega da camada de cima
     * (ou seja, a camada de cima chamou layerDOWN.send(mensagem))
     * <p>Obs.: Mensagens vindas da camada de cima ficam na fila 'sentMessages'
     */
    protected abstract void sendMessage();

    /**
     * Metodo executado sempre quando uma mensagem chega da camada de baixo
     * (ou seja, a camada de baixo chamou layerUP.receive(mensagem))
     * <p>Obs.: Mensagens vindas da camada de baixo ficam na fila 'receivedMessages'
     */
    protected abstract void receiveMessage();

    /**
     * Inicia duas threads que ativa os metodos apropriados quando chegam novas mensagens
     */
    public void start() {
        new ReceivingThread().start();
        new SendingThread().start();
    }

    /**
     * Thread que executa sendMessage() quando ha mensagens
     * na fila 'sentMessages', e dorme quando nao ha.
     */
    private class SendingThread extends Thread {
        @Override
        public void run() {
            try {
                while (true) {
                    semaphoreSend.acquire();
                    if(printTracks)
                        System.out.println(name + ": Nova mensagem a enviar " + sentMessages.getFirst());
                    sendMessage();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Thread que executa receiveMessage() quando ha mensagens
     * na fila 'receivedMessages', e dorme quando nao ha.
     */
    private class ReceivingThread extends Thread {
        @Override
        public void run() {
            try {
                while (true) {
                    semaphoreReceive.acquire();
                    if(printTracks)
                        System.out.println(name + ": Nova mensagem recebida " + receivedMessages.getFirst());
                    receiveMessage();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void setDebugName(String name){
        this.name = name;
    }
}
